package com.jltx.storage.downloader;

import android.content.Context;


import com.jltx.storage.downloader.net.DownloadFileMultiCall;
import com.jltx.storage.downloader.net.DownloadMultiAsyncCall;
import com.jltx.storage.downloader.util.OkhttpClientFactoryUtils;
import com.jltx.storage.downloader.util.RxSchedulerUtil;
import com.jltx.zeus.observable.CallObservableAdapter;
import com.jltx.zeus.observable.ObservableOnSubscribeEx;

import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import io.reactivex.Observable;
import io.reactivex.ObservableSource;
import io.reactivex.Observer;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable;
import io.reactivex.functions.Function;
import okhttp3.OkHttpClient;
import okhttp3.ResponseBody;
import retrofit2.Retrofit;
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;
import retrofit2.converter.gson.GsonConverterFactory;
import retrofit2.converter.scalars.ScalarsConverterFactory;
import retrofit2.http.GET;
import retrofit2.http.Streaming;
import retrofit2.http.Url;

/**
 * @author jltxseo
 * Created by junlintianxia on 2019/12/02.
 */
class Downloader {
    private static final String TAG = "Downloader";

    private static class SingletonHolder {
        private static Downloader INSTANCE = new Downloader();
    }

    protected static Downloader getInstance() {
        return SingletonHolder.INSTANCE;
    }

    private interface BaseApiService {
        /**
         * 通用文件下载接口
         *
         * @param fileUrl
         * @return
         */
        @Streaming
        @GET
        Observable<ResponseBody> downloadFile(@Url String fileUrl);
    }

    private Context context;
    private Retrofit mRetrofit;
    private BaseApiService apiService;
    private CompositeDisposable compositeDisposable;
    private Map<Object, Disposable> mCurDisposableMap = new ConcurrentHashMap<>();

    private Downloader() {
        if (compositeDisposable == null) {
            compositeDisposable = new CompositeDisposable();
        }
    }

    protected void init(Context context) {
        this.context = context.getApplicationContext();
        RxSchedulerUtil.setRxJavaErrorHandler();
    }

    protected Context getContext() {
        return context;
    }

    /**
     * 使用前必须初始化
     *
     * @param okHttpClient
     */
    protected void initHttpClient(OkHttpClient okHttpClient) {
        if (mRetrofit == null && okHttpClient != null) {
            Retrofit.Builder builder = new Retrofit.Builder()
                    .baseUrl("http://www.baidu.com")
                    .addConverterFactory(ScalarsConverterFactory.create())
                    .addConverterFactory(GsonConverterFactory.create())
                    .addCallAdapterFactory(RxJava2CallAdapterFactory.create());
            builder.client(okHttpClient);
            mRetrofit = builder.build();
        }
    }

    protected void addDisposable(Disposable disposable) {
        if (compositeDisposable == null) {
            compositeDisposable = new CompositeDisposable();
        }
        compositeDisposable.add(disposable);
    }

    protected boolean removeDisposableFromMap(Disposable disposable) {
        if (disposable != null && compositeDisposable != null) {
            return compositeDisposable.remove(disposable);
        }
        return false;
    }


    protected void unSubscribe() {
        if (compositeDisposable != null) {
            compositeDisposable.clear();
        }
    }

    /**
     * 通过tag取消下载任务
     *
     * @param tag
     */
    protected void cancel(Object tag) {
        if (tag != null && mCurDisposableMap.containsKey(tag)) {
            removeDisposableFromMap(tag);
        }
    }


    protected void addDisposableToMap(Object tag, Disposable disposable) {
        synchronized (mCurDisposableMap) {
            mCurDisposableMap.put(tag, disposable);
        }
        addDisposable(disposable);
    }


    protected void removeDisposableFromMap(Object tag) {
        synchronized (mCurDisposableMap) {
            if (mCurDisposableMap.containsKey(tag)) {
                Disposable disposable = mCurDisposableMap.get(tag);
                mCurDisposableMap.remove(tag);
                removeDisposableFromMap(disposable);
            }
        }
    }

    /**
     * create you ApiService
     * Create an implementation of the API endpoints defined by the {@code service} interface.
     */
    public <T> T create(final Class<T> service) {
        if (service == null) {
            throw new RuntimeException("Api service is null!");
        }
        return mRetrofit.create(service);
    }

    /**
     * create BaseApi  defalte ApiManager
     *
     * @return ApiManager
     */
    public Downloader createBaseApi() {
        if (mRetrofit == null) {
            initHttpClient(OkhttpClientFactoryUtils.getInstance().getOkHttpClient());
        }
        apiService = create(BaseApiService.class);
        return this;
    }

    /**
     * 下载单个文件
     *
     * @param tag          标记下载任务的tag
     * @param url          要下载的文件的url链接
     * @param localPath    要下载的文件输出本地全路径
     * @param fileCallback 回调监听器
     */
    protected void download(final Object tag, final String url, final String localPath, final FileCallback<RequestResult> fileCallback) {
        if (apiService == null) {
            createBaseApi();
        }

        downloadObservable(url, localPath)
                //对error的兜底
                .onErrorReturn(new Function<Throwable, RequestResult>() {
                    @Override
                    public RequestResult apply(Throwable throwable) throws Exception {
                        return new RequestResult(localPath, url, RequestStatus.STATUS_FAIL, 0, 0, RequestException.transformException(throwable));
                    }
                })
                .compose(RxSchedulerUtil.<RequestResult>applyIO2MainSchedulersTransformer())
                .subscribe(new Observer<RequestResult>() {
                    @Override
                    public void onSubscribe(Disposable d) {
                        addDisposableToMap(tag, d);
                        if (fileCallback != null) {
                            fileCallback.onSubscribe(d);
                        }
                    }

                    @Override
                    public void onNext(RequestResult requestResult) {
                        if (requestResult.status == RequestStatus.STATUS_LOADING) {
                            if (fileCallback != null) {
                                fileCallback.onLoading(tag, requestResult);
                            }
                        } else if (requestResult.status == RequestStatus.STATUS_FAIL) {
                            if (fileCallback != null) {
                                fileCallback.onFailure(tag, requestResult);
                            }
                        } else {
                            if (fileCallback != null) {
                                fileCallback.onSuccess(tag, requestResult);
                            }
                        }
                    }

                    @Override
                    public void onError(Throwable e) {
                        removeDisposableFromMap(tag);
                    }

                    @Override
                    public void onComplete() {
                        removeDisposableFromMap(tag);
                    }
                });
    }

    /**
     * 多个视频批量下载
     *
     * @param tag
     * @param urlList
     * @param localPathList
     * @param multiFileCallback
     */
    protected void downloadMulti(final Object tag, final List<String> urlList, final List<String> localPathList, final FileCallback<MultiRequestResult> multiFileCallback) {
        downloadMulti(tag, urlList, localPathList, multiFileCallback, null);
    }

    /**
     * 多个视频批量下载
     *
     * @param tag
     * @param urlList
     * @param localPathList
     * @param multiFileCallback
     * @param fileCallback
     */
    protected void downloadMulti(final Object tag, final List<String> urlList, final List<String> localPathList, final FileCallback<MultiRequestResult> multiFileCallback, final FileCallback<RequestResult> fileCallback) {
        if (urlList != null && urlList.size() > 0 && localPathList != null && urlList.size() == localPathList.size()) {
            downloadMultiObservable(urlList, localPathList, fileCallback)
                    //对error的兜底
                    .onErrorReturn(new Function<Throwable, MultiRequestResult>() {
                        @Override
                        public MultiRequestResult apply(Throwable throwable) throws Exception {
                            return new MultiRequestResult(RequestStatus.STATUS_FAIL, 0, 0, RequestException.transformException(throwable));
                        }
                    })
                    .compose(RxSchedulerUtil.<MultiRequestResult>applyIO2MainSchedulersTransformer())
                    .subscribe(new Observer<MultiRequestResult>() {
                        @Override
                        public void onSubscribe(Disposable d) {
                            addDisposableToMap(tag, d);
                            if (multiFileCallback != null) {
                                multiFileCallback.onSubscribe(d);
                            }
                        }

                        @Override
                        public void onNext(MultiRequestResult multiRequestResult) {
                            if (multiRequestResult.status == RequestStatus.STATUS_LOADING) {
                                if (multiFileCallback != null) {
                                    multiFileCallback.onLoading(tag, multiRequestResult);
                                }
                            } else if (multiRequestResult.status == RequestStatus.STATUS_SUCCESS) {
                                if (multiFileCallback != null) {
                                    multiFileCallback.onSuccess(tag, multiRequestResult);
                                }
                            } else {
                                if (multiFileCallback != null) {
                                    multiFileCallback.onFailure(tag, multiRequestResult);
                                }
                            }
                        }

                        @Override
                        public void onError(Throwable e) {
                            removeDisposableFromMap(tag);
                        }

                        @Override
                        public void onComplete() {
                            removeDisposableFromMap(tag);
                        }
                    });
        } else {
            if (multiFileCallback != null) {
                MultiRequestResult multiRequestResult = new MultiRequestResult(RequestStatus.STATUS_FAIL, 0, 0, new RequestException(RequestException.CODE_ERROR_OTHER_EXCEPTION, "url list or local list error"));
                multiFileCallback.onFailure(tag, multiRequestResult);
            }
        }
    }

    /**
     * 获取没有进度的单个下载的Observable
     * onNext回调只一次，就是下载成功回调的
     *
     * @param url
     * @param localPath
     * @return
     */
    protected Observable<RequestResult> downloadObservableNoProgress(final String url, final String localPath) {
        return downloadObservable(url, localPath).takeLast(1);
    }

    /**
     * 获取带进度的单个下载的Observable
     * 进度会在onNext回调多次
     *
     * @param url
     * @param localPath
     * @return
     */
    protected Observable<RequestResult> downloadObservable(final String url, final String localPath) {
        if (apiService == null) {
            createBaseApi();
        }

        return apiService.downloadFile(url)
                .flatMap(new Function<ResponseBody, ObservableSource<RequestResult>>() {
                    @Override
                    public ObservableSource<RequestResult> apply(ResponseBody responseBody) throws Exception {
                        return CallObservableAdapter.adapt(new DownloadFileMultiCall(localPath, url, responseBody));
                    }
                });

    }

    /**
     * 不带进度的批量下载
     * 在onNext回调一次，成功或者失败都只会调用一次
     *
     * @param urlList
     * @param localPathList
     * @return
     */
    protected Observable<MultiRequestResult> downloadMultiObservableNoProgress(final List<String> urlList, final List<String> localPathList) {
        return downloadMultiObservableNoProgress(urlList, localPathList, null);
    }


    /**
     * 不带进度的批量下载
     * 在onNext回调一次，成功或者失败都只会调用一次
     *
     * @param urlList
     * @param localPathList
     * @param fileCallback
     * @return
     */
    protected Observable<MultiRequestResult> downloadMultiObservableNoProgress(final List<String> urlList, final List<String> localPathList, final FileCallback fileCallback) {
        return downloadMultiObservable(urlList, localPathList, fileCallback).takeLast(1);
    }

    /**
     * 批量下载
     * 进度会在onNext回调多次,进度，成功，失败都在onNext里面回调
     *
     * @param urlList
     * @param localPathList
     * @param fileCallback
     * @return
     */
    protected Observable<MultiRequestResult> downloadMultiObservable(final List<String> urlList, final List<String> localPathList, final FileCallback fileCallback) {
        if (urlList != null && urlList.size() > 0 && localPathList != null && urlList.size() == localPathList.size()) {
            return CallObservableAdapter.adapt(new DownloadMultiAsyncCall(urlList, localPathList, fileCallback));
        }

        //对输入的批量下载数据进行错误回调兜底
        return Observable.create(new ObservableOnSubscribeEx<MultiRequestResult>() {
            @Override
            public void subscribe(ObservableEmitter<MultiRequestResult> emitter) throws Exception {
                MultiRequestResult multiRequestResult = new MultiRequestResult(RequestStatus.STATUS_FAIL, 0, 0, new RequestException(RequestException.CODE_ERROR_OTHER_EXCEPTION, "url list or local list error"));
                emitter.onNext(multiRequestResult);
                emitter.onComplete();
            }
        });
    }


}
